home *** CD-ROM | disk | FTP | other *** search
- (**********************************************************************)
- (* ASYNCHRONOUS I/O ROUTINES *)
- (* Version 1.01, 4/8/85 *)
- (* *)
- (* The following are data definitions and procedures which perform *)
- (* I/O functions for IBM PC asynchronous communications adapters. *)
- (* Comments here and there, below, explain what's what. Enjoy! *)
- (* *)
- (* Although these routines are set-up to support COM1 and COM2, which *)
- (* may be open simultaneously if you wish, it's probably not too much *)
- (* of an effort to make them support more - like in the PC/AT. Also, *)
- (* it's a trivial excercise to support up to 9600 baud - presuming *)
- (* the machine is fast enough. You might also consider adding such *)
- (* stuff as XON/XOFF pacing, port-to-port communication, and so on. *)
- (* *)
- (* In any case, writing this took a bunch of groping around, head *)
- (* scratching, trial and error, plus cursing at IBM's inadequate and *)
- (* obscure documentation on the subject. It's almost like they don't *)
- (* want the general public to write stuff like this. Too bad, IBM! *)
- (* *)
- (* Written and placed in the public domain by: *)
- (* Glen F. Marshall *)
- (* 1006 Gwilym Circle *)
- (* Berwyn, PA 19312 *)
- (**********************************************************************)
-
- type
- ComPort = (Com1,Com2); { Asynchronous ports }
- ComSpeed = (LowSpeed,MedSpeed,HighSpeed); { 300/1200/2400 baud }
- ComParity = (NoParity,EvenParity,OddParity); { no/even/odd parity }
- ComInBuffer = array[0..4095] of byte; { 4K ring buffer }
- ComOutBuffer = array[0..4095] of byte; { 4K ring buffer }
- ComCtlPtr = ^ComCtlRec; { Ptr, port control }
- ComCtlRec = record
- ComCtlSpeed: ComSpeed; { current baud rate }
- ComCtlParity: ComParity; { current parity }
- LastComIdent: byte; { interrupt reason }
- LastComLineStat: byte; { line status }
- LastComModemStat: byte; { modem status }
- SavedVector: ^byte; { pre-open vector }
- InputInIx: integer; { ring buffer index }
- InputOutIx: integer; { ring buffer index }
- OutputOutIx: integer; { ring buffer index }
- OutputInIx: integer; { ring buffer index }
- InputBuffer: ComInBuffer; { input ring buffer }
- OutputBuffer: ComOutBuffer;{ output ring buffer }
- end;
-
- const
- AsyncDS: integer = -1; { NOTE: contrary to the idea of constants,
- but to give the interrupt handler access
- to the ComCtl array, this constant is
- modified. See StoreDS. }
- ComInt: array[Com1..Com2] of byte = ($C,$B);
- ComData = 0; { Communications port data xmit/recv }
- ComEnable = 1; { Communications port interrupt enable }
- ComEnableRD = 1; { data received }
- ComEnableTX = 2; { transmit register empty }
- ComEnableLS = 4; { line status }
- ComEnableMS = 8; { modem status }
- ComIdent = 2; { Communications port interrupt identity }
- ComIdentNot = 1; { interrupt not pending }
- ComIdentMS = 0; { modem status interrupt }
- ComIdentTX = 2; { transmit register empty }
- ComIdentRD = 4; { data received }
- ComIdentLS = 6; { line status interrupt }
- ComLineCtl = 3; { Communications port line control }
- ComLineInit: array[NoParity..OddParity] of byte = ($03,$1A,$0A);
- ComLineBreak = 64; { Communications Line Send Break }
- ComLineDLAB = 128; { Communications Divisor Latch Access Bit }
- ComModemCtl = 4; { Communications port modem control }
- ComModemDTR = 1; { data terminal ready }
- ComModemRTS = 2; { request to send }
- ComModemOUT1 = 4; { Out 1 signal (enables ring) }
- ComModemOUT2 = 8; { Out 2 signal (enables data receive) }
- ComLineStat = 5; { Communications port line status }
- ComLineOR = 2; { overrun }
- ComLinePE = 4; { parity error }
- ComLineFE = 8; { framing error }
- ConLineBI = 16; { break interrupt }
- ComLineTHRE = 32; { transmit holding register empty }
- ComModemStat = 6; { Communications port modem status }
- ComModemCTS = 16; { clear to send }
- ComModemDSR = 32; { data set ready }
- ComModemRI = 64; { phone ring }
- ComModemCD = 128; { carrier detect }
- ComDivLo = 0; { Communications port baud-rate divisor 1 }
- ComDivHi = 1; { Communications port baud-rate divisor 2 }
- ComBaudDiv: array[LowSpeed..HighSpeed] of integer = (384,96,48);
- Cmd_8259 = $20; { 8259 interrupt controller command port }
- EOI_8259 = $20; { "End Of Interrupt" command }
- RIL_8259 = $0B; { "Report Interrupt Level" command }
- Msk_8259 = $21; { 8259 interrupt controller mask port }
- MskVal_8259: array[Com1..Com2] of byte = ($EF,$F7);
-
- var
- ComBase: array[Com1..Com2] of integer absolute $0040:0000;
- ComPtr: array[Com1..Com2] of ComCtlPtr; { for each comm port }
-
- { This is called to modify AsyncDS }
- procedure StoreDS(var SavedDS: integer);
- begin
- savedDS := DSEG;
- end;
-
- { This turns the communications interrupts on or off }
- procedure Set_8259(ComDev: ComPort; Sw: boolean);
- begin
- inline($FA); {stop interrupts while we do this}
- case Sw of
- true: port[Msk_8259] :=
- port[Msk_8259] and MskVal_8259[ComDev];
- false: port[Msk_8259] :=
- port[Msk_8259] or ($FF xor MskVal_8259[ComDev]);
- end;
- inline($FB); {reenable interrupts}
- end {Set_8259};
-
- { Communications interrupt handler: do NOT call this! }
- procedure AsyncInterrupt;
- var
- pushes: array[1..8] of integer; {allows for inline pushes}
- ComDev: ComPort;
- begin
- inline($83/$C4/$11/ { add sp,11h (fool turbo) }
- $50/ { push ax }
- $53/ { push bx }
- $51/ { push cx }
- $52/ { push dx }
- $56/ { push si }
- $57/ { push di }
- $1E/ { push ds }
- $06/ { push es }
- $4C/ { dec sp (fool turbo) }
- $2E/$8E/$1E/AsyncDS); { mov ds,CS:AsyncDS }
- port[Cmd_8259] := RIL_8259; { find out which COM port interrupted }
- if (port[Cmd_8259] and ($FF xor MskVal_8259[Com1])) <> 0
- then ComDev := Com1
- else ComDev := Com2;
- with ComPtr[ComDev]^ do { Having deduced the port, find out why }
- begin
- LastComIdent := Port[ComBase[ComDev]+ComIdent];
- if (LastComIdent and ComIdentNot) = 0 then { There is a reason }
- begin
- case LastComIdent of
- ComIdentLS: LastComLineStat :=
- port[ComBase[ComDev]+ComLineStat];
- ComIdentMS: LastComModemStat :=
- port[ComBase[ComDev]+ComModemStat];
- ComIdentRD: begin
- if ((InputInIx+1) mod sizeof(InputBuffer))
- <> InputOutIx then
- begin { store input character in ring buffer }
- InputBuffer[InputInIx] :=
- port[ComBase[ComDev]+ComData];
- InputInIx :=
- (InputInIx+1) mod sizeof(InputBuffer);
- end
- else LastComLineStat := { buffer overrun }
- LastComLineStat or ComLineOR;
- end;
- ComIdentTX: if OutputOutIx <> OutputInIx then
- begin { write output character from ring buffer }
- port[ComBase[ComDev]+ComData] :=
- OutputBuffer[OutputOutIx];
- OutputOutIx :=
- (OutputOutIx+1) mod sizeof(OutputBuffer);
- end;
- end;
- end;
- end;
- port[Cmd_8259] := EOI_8259;
- inline($44/ { inc sp (unfool turbo) }
- $07/ { pop es }
- $1F/ { pop ds }
- $5F/ { pop di }
- $5E/ { pop si }
- $5A/ { pop dx }
- $59/ { pop cx }
- $5B/ { pop bx }
- $58/ { pop ax }
- $8B/$E5/ { mov sp,bp }
- $5D/ { pop bp }
- $CF); { iret }
- end {AsyncInterrupt};
-
- { This modifies the communications interrupt vector }
- procedure SetInterrupt(ComDev: ComPort; Sw: boolean);
- var
- MsDosRegs: record
- AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: integer;
- end;
- begin
- inline($FA); {stop interrupts while we do this}
- with MsDosRegs, ComPtr[ComDev]^ do
- begin
- case Sw of
- true: begin
- AX := $3500+ComInt[ComDev]; { get interrupt vector}
- MsDos(MsDosRegs);
- SavedVector := Ptr(ES,BX);
- DS := CSEG;
- DX := ofs(AsyncInterrupt);
- end;
- false: begin
- DS := seg(SavedVector^);
- DX := ofs(SavedVector^);
- end;
- end;
- AX := $2500+ComInt[ComDev]; { set interrupt vector }
- MsDos(MsDosRegs)
- end;
- inline($FB); {reenable interrupts}
- end {SetInterrupt};
-
- { This sets the communications baud rate }
- procedure SetSpeed(ComDev: ComPort; ComRate: ComSpeed);
- begin
- inline($FA); {stop interrupts while we do this}
- with ComPtr[ComDev]^ do ComCtlSpeed := ComRate;
- port[ComBase[ComDev]+ComLineCtl] :=
- port[ComBase[ComDev]+ComLineCtl] or ComLineDLAB;
- port[ComBase[ComDev]+ComDivLo] := lo(ComBaudDiv[ComRate]);
- port[ComBase[ComDev]+ComDivHi] := hi(ComBaudDiv[ComRate]);
- port[ComBase[ComDev]+ComLineCtl] :=
- port[ComBase[ComDev]+ComLineCtl] and ($FF xor ComLineDLAB);
- inline($FB); {reenable interrupts}
- end {SetSpeed};
-
- { This modifies the line control register for parity & data bits }
- procedure SetFormat(ComDev: ComPort; ComFormat: ComParity);
- begin
- inline($FA); {stop interrupts while we do this}
- with ComPtr[ComDev]^ do ComCtlParity := ComFormat;
- port[ComBase[ComDev]+ComLineCtl] := ComLineInit[ComFormat];
- inline($FB); {reenable interrupts}
- end {SetFormat};
-
- { This modifies the modem control register for DTR, RTS, etc. }
- procedure SetModem(ComDev: ComPort; ModemCtl: byte);
- begin
- inline($FA); {stop interrupts while we do this}
- port[ComBase[ComDev]+ComModemCtl] := ModemCtl;
- inline($FB); {reenable interrupts}
- end {SetModem};
-
- { This modifies the communications interrupt enable register }
- procedure SetEnable(ComDev: ComPort; Enable: byte);
- begin
- inline($FA); {stop interrupts while we do this}
- port[ComBase[ComDev]+ComEnable] := Enable;
- inline($FB); {reenable interrupts}
- end {SetModem};
-
- { This procedure MUST be called before doing any I/O. }
- procedure ComOpen(ComDev: ComPort;
- ComRate: ComSpeed; ComFormat: ComParity);
- begin
- Set_8259(ComDev,false);
- StoreDS(AsyncDS);
- new(ComPtr[ComDev]);
- with ComPtr[ComDev]^ do
- begin
- LastComIdent := 1;
- InputInIx := 0;
- InputOutIx := 0;
- OutputOutIx := 0;
- OutputInIx := 0;
- end;
- SetInterrupt(ComDev,true);
- Set_8259(ComDev,true);
- SetEnable(ComDev, ComEnableRD+ComEnableTX+ComEnableLS+ComEnableMS);
- SetModem(ComDev, ComModemDTR+ComModemRTS+ComModemOUT1+ComModemOUT2);
- SetSpeed(ComDev, ComRate);
- SetFormat(ComDev, ComFormat);
- with ComPtr[ComDev]^ do
- begin
- LastComLineStat := port[ComBase[ComDev]+ComLineStat];
- LastComModemStat := port[ComBase[ComDev]+ComModemStat];
- end;
- end {ComOpen};
-
- { This procedure shuts-down communications }
- procedure ComClose(ComDev: ComPort);
- begin
- Set_8259(ComDev,false);
- SetEnable(ComDev, $00);
- SetInterrupt(ComDev, false);
- SetModem(ComDev, $00);
- port[ComBase[ComDev]+ComLineCtl] := $00;
- dispose(ComPtr[ComDev]);
- end {ComClose};
-
- { This procedure tells you if there's any input }
- function ComInReady(ComDev: ComPort): boolean;
- begin
- with ComPtr[ComDev]^ do ComInReady := InputInIx <> InputOutIx;
- end {ComInReady};
-
- { This procedure reads input from the ring buffer }
- function ComIn(ComDev: ComPort): char;
- begin
- with ComPtr[ComDev]^ do
- begin
- repeat until ComInReady(ComDev);
- ComIn := chr(InputBuffer[InputOutIx]);
- InputOutIx := (InputOutIx+1) mod sizeof(InputBuffer);
- end;
- end {ComIn};
-
- { This procedure writes output, filling the ring buffer if necessary. }
- procedure ComOut(ComDev: ComPort; Data: char);
- begin
- with ComPtr[ComDev]^ do
- begin
- if ((port[ComBase[ComDev]+ComLineStat] and ComLineTHRE) <> 0) and
- (OutputInIx = OutputOutIx) then
- begin
- port[ComBase[ComDev]+ComData] := ord(Data);
- end
- else
- begin
- repeat
- until ((OutputInIx+1) mod sizeof(outputBuffer)) <> OutputOutIx;
- OutputBuffer[OutputInIx] := ord(Data);
- OutputInIx := (OutputInIx+1) mod sizeof(OutputBuffer);
- end;
- end;
- end {ComOut};